/**
 * WebSocket 工具类
 * 提供 WebSocket 连接管理、消息推送等功能
 * 
 * 文件说明：
 * - 这是一个通用的 WebSocket 客户端封装
 * - 提供了完整的连接生命周期管理
 * - 支持自动重连、心跳保活、消息路由等功能
 * 
 * @module utils/websocket
 */

/**
 * WebSocket 客户端类
 * 
 * 功能说明：
 * 1. 管理 WebSocket 连接的完整生命周期（连接、重连、断开）
 * 2. 提供自动重连机制，网络断开时自动尝试重连
 * 3. 实现心跳保活机制，防止连接被服务器主动关闭
 * 4. 支持消息类型路由，可以根据消息类型注册不同的处理器
 * 5. 提供事件回调接口，方便外部监听连接状态和消息
 */
class WebSocketClient {
  constructor(url, options = {}) {
    this.url = url  // WebSocket 服务器地址
    this.options = {
      reconnectInterval: 3000,      // 重连间隔（毫秒）- 连接断开后等待3秒再重连
      maxReconnectAttempts: 5,      // 最大重连次数 - 避免无限重连造成资源浪费
      heartbeatInterval: 30000,    // 心跳间隔（毫秒）- 每30秒发送一次心跳保持连接
      ...options
    }
    this.ws = null                 // WebSocket 原生对象
    this.reconnectAttempts = 0     // 当前重连尝试次数
    this.isManualClose = false     // 是否为手动关闭（手动关闭不自动重连）
    this.messageHandlers = new Map()  // 消息类型处理器映射表，key为消息类型，value为处理器数组
    this.status = 'disconnected'    // 连接状态：disconnected(未连接), connecting(连接中), connected(已连接), reconnecting(重连中)
    this.heartbeatTimer = null     // 心跳定时器ID，用于清理心跳
    
    // 事件回调函数（由外部设置）
    this.onOpen = null      // 连接成功时触发
    this.onClose = null     // 连接关闭时触发
    this.onError = null     // 连接错误时触发
    this.onMessage = null   // 收到消息时触发
  }

  /**
   * 连接到服务器
   * 
   * 工作流程：
   * 1. 检查当前连接状态，避免重复连接
   * 2. 创建新的 WebSocket 连接对象
   * 3. 注册事件处理器（onopen, onmessage, onerror, onclose）
   * 4. 连接成功后启动心跳保活机制
   */
  connect() {
    // 防止重复连接：如果已经连接，直接返回
    if (this.ws && this.ws.readyState === WebSocket.OPEN) {
      console.log('[WebSocket] 已经连接，无需重复连接')
      return
    }

    // 防止重复连接：如果正在连接中，等待当前连接完成
    if (this.ws && this.ws.readyState === WebSocket.CONNECTING) {
      console.log('[WebSocket] 正在连接中，请稍候...')
      return
    }

    // 重置手动关闭标志，允许自动重连
    this.isManualClose = false
    this.status = 'connecting'  // 更新状态为连接中
    
    try {
      console.log(`[WebSocket] 正在连接到: ${this.url}`)
      // 创建 WebSocket 连接实例
      this.ws = new WebSocket(this.url)

      // ============ 连接成功事件 ============
      // 当 WebSocket 连接成功建立时触发
      this.ws.onopen = (event) => {
        console.log('[WebSocket] 连接成功')
        this.status = 'connected'           // 更新状态为已连接
        this.reconnectAttempts = 0          // 重置重连计数（连接成功）
        
        // 启动心跳保活机制（防止服务器因超时关闭连接）
        this.startHeartbeat()
        
        // 触发外部设置的成功回调
        if (this.onOpen) {
          this.onOpen(event)
        }
      }

      // ============ 接收消息事件 ============
      // 当服务器发送消息到客户端时触发
      this.ws.onmessage = (event) => {
        try {
          // 过滤心跳消息：ping/pong 是心跳协议，不需要业务处理
          if (event.data === 'pong' || event.data === 'ping') {
            return  // 直接返回，不进行后续处理
          }

          // 尝试解析 JSON 格式的消息
          // 如果解析失败，则作为普通文本消息处理
          let data
          try {
            data = JSON.parse(event.data)  // 尝试解析为 JSON 对象
          } catch {
            // 解析失败，包装为统一的文本消息格式
            data = { type: 'text', content: event.data }
          }

          // 1. 触发通用消息回调（onMessage 是所有消息的统一入口）
          if (this.onMessage) {
            this.onMessage(data, event)
          }

          // 2. 触发特定类型的处理器（根据消息的 type 字段路由到对应处理器）
          // 例如：{type: 'notification', content: 'xxx'} 会触发 'notification' 类型的处理器
          if (data.type && this.messageHandlers.has(data.type)) {
            const handlers = this.messageHandlers.get(data.type)
            handlers.forEach(handler => handler(data, event))
          }

          // 3. 触发通用消息处理器（'*' 表示匹配所有类型的消息）
          // 用于全局消息处理，比如日志记录、消息统计等
          if (this.messageHandlers.has('*')) {
            const handlers = this.messageHandlers.get('*')
            handlers.forEach(handler => handler(data, event))
          }
        } catch (error) {
          console.error('[WebSocket] 处理消息失败:', error)
        }
      }

      // ============ 连接错误事件 ============
      // 当连接发生错误时触发（网络问题、服务器问题等）
      this.ws.onerror = (error) => {
        console.error('[WebSocket] 连接错误:', error)
        this.status = 'disconnected'  // 更新状态为未连接
        
        // 触发外部设置的错误回调
        if (this.onError) {
          this.onError(error)
        }
      }

      // ============ 连接关闭事件 ============
      // 当 WebSocket 连接关闭时触发（可能是正常关闭或异常断开）
      this.ws.onclose = (event) => {
        console.log('[WebSocket] 连接关闭:', event.code, event.reason)
        this.status = 'disconnected'  // 更新状态为未连接
        this.stopHeartbeat()          // 停止心跳（连接已断开）
        
        // 触发外部设置的关闭回调
        if (this.onClose) {
          this.onClose(event)
        }

        // 自动重连机制：
        // 1. 必须是意外断开（非手动关闭）
        // 2. 未超过最大重连次数
        // 满足条件则自动重连，否则停止重连
        if (!this.isManualClose && this.reconnectAttempts < this.options.maxReconnectAttempts) {
          this.reconnect()  // 尝试重连
        } else if (this.reconnectAttempts >= this.options.maxReconnectAttempts) {
          console.error('[WebSocket] 达到最大重连次数，停止重连')
        }
      }
    } catch (error) {
      console.error('[WebSocket] 连接异常:', error)
      this.status = 'disconnected'
      
      if (!this.isManualClose) {
        this.reconnect()
      }
    }
  }

  /**
   * 手动断开连接
   * 
   * 说明：
   * - 设置 isManualClose 标志，防止自动重连
   * - 清理心跳定时器，释放资源
   * - 关闭 WebSocket 连接
   */
  disconnect() {
    this.isManualClose = true  // 标记为手动关闭（防止自动重连）
    this.stopHeartbeat()       // 停止心跳定时器
    
    // 如果连接存在，关闭它
    if (this.ws) {
      this.ws.close()  // 关闭 WebSocket 连接
      this.ws = null   // 清空引用，释放内存
    }
    
    this.status = 'disconnected'  // 更新状态
    console.log('[WebSocket] 手动断开连接')
  }

  /**
   * 发送消息到服务器
   * 
   * @param {string|object} data - 要发送的消息（字符串或对象）
   * @returns {boolean} 发送是否成功
   * 
   * 说明：
   * - 发送前检查连接状态，只有 OPEN 状态才能发送
   * - 支持字符串和对象两种格式，对象会自动序列化为 JSON
   * - 发送失败时返回 false，不抛出异常
   */
  send(data) {
    // 检查连接状态：只有 OPEN 状态才能发送消息
    // WebSocket.OPEN = 1 表示连接已建立且可以通信
    if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
      console.warn('[WebSocket] 连接未就绪，无法发送消息')
      return false
    }

    try {
      // 统一消息格式：如果是对象则序列化为 JSON，字符串直接发送
      const message = typeof data === 'string' ? data : JSON.stringify(data)
      this.ws.send(message)  // 发送消息到服务器
      return true
    } catch (error) {
      console.error('[WebSocket] 发送消息失败:', error)
      return false
    }
  }

  /**
   * 自动重连机制
   * 
   * 工作流程：
   * 1. 检查是否为手动关闭（手动关闭不自动重连）
   * 2. 增加重连计数
   * 3. 等待指定间隔后重新调用 connect()
   * 
   * 说明：
   * - 使用指数退避或固定间隔重连
   * - 达到最大重连次数后停止（避免无限重连）
   */
  reconnect() {
    // 如果是手动关闭，不进行自动重连
    if (this.isManualClose) {
      return
    }

    this.reconnectAttempts++           // 增加重连尝试次数
    this.status = 'reconnecting'       // 更新状态为重连中
    
    console.log(`[WebSocket] ${this.options.reconnectInterval}ms 后尝试重连 (${this.reconnectAttempts}/${this.options.maxReconnectAttempts})`)
    
    // 延迟重连：等待指定时间后再尝试连接（避免频繁连接造成服务器压力）
    setTimeout(() => {
      // 再次检查是否为手动关闭（防止在等待期间用户手动关闭了连接）
      if (!this.isManualClose) {
        this.connect()  // 重新连接
      }
    }, this.options.reconnectInterval)
  }

  /**
   * 启动心跳保活机制
   * 
   * 作用：
   * - 定期向服务器发送心跳消息（ping），证明连接仍然活跃
   * - 防止服务器因长时间无数据交互而主动关闭连接
   * - 帮助检测网络异常（如果心跳发送失败，可能是连接已断开）
   * 
   * 原理：
   * - 使用 setInterval 定时发送心跳
   * - 只在连接为 OPEN 状态时发送
   */
  startHeartbeat() {
    // 先停止旧的心跳（防止重复启动）
    this.stopHeartbeat()
    
    // 创建定时器，每隔 heartbeatInterval 毫秒发送一次心跳
    this.heartbeatTimer = setInterval(() => {
      // 只在连接有效时发送心跳
      if (this.ws && this.ws.readyState === WebSocket.OPEN) {
        this.ws.send('ping')  // 发送心跳消息
      }
    }, this.options.heartbeatInterval)
  }

  /**
   * 停止心跳保活机制
   * 
   * 说明：
   * - 清理心跳定时器，释放资源
   * - 通常在连接断开或手动关闭时调用
   */
  stopHeartbeat() {
    if (this.heartbeatTimer) {
      clearInterval(this.heartbeatTimer)  // 清除定时器
      this.heartbeatTimer = null          // 清空引用
    }
  }

  /**
   * 注册消息类型处理器
   * 
   * @param {string} type - 消息类型（如 'notification', 'chat' 等，'*' 表示所有消息）
   * @param {function} handler - 处理函数，接收 (data, event) 参数
   * @returns {function} 取消注册函数，调用后移除该处理器
   * 
   * 使用示例：
   * const unsubscribe = ws.onMessageType('notification', (data) => {
   *   console.log('收到通知:', data)
   * })
   * // 稍后取消订阅
   * unsubscribe()
   */
  onMessageType(type, handler) {
    // 如果该类型还没有处理器数组，创建一个
    if (!this.messageHandlers.has(type)) {
      this.messageHandlers.set(type, [])
    }
    // 将处理器添加到对应类型的数组中
    this.messageHandlers.get(type).push(handler)
    
    // 返回取消注册函数（用于清理，防止内存泄漏）
    return () => {
      const handlers = this.messageHandlers.get(type)
      const index = handlers.indexOf(handler)
      if (index > -1) {
        handlers.splice(index, 1)  // 从数组中移除该处理器
      }
    }
  }

  /**
   * 获取连接状态字符串
   * 
   * @returns {string} 状态：'disconnected' | 'connecting' | 'connected' | 'reconnecting'
   */
  getStatus() {
    return this.status
  }

  /**
   * 检查是否已连接
   * 
   * @returns {boolean} true 表示连接已建立且可以通信
   * 
   * 说明：
   * - WebSocket.OPEN = 1 表示连接已打开，可以发送和接收消息
   * - 比 getStatus() 更准确，因为它检查的是 WebSocket 原生对象的状态
   */
  isConnected() {
    return this.ws && this.ws.readyState === WebSocket.OPEN
  }
}

/**
 * 创建 WebSocket 客户端实例
 * @param {string} clientId - 客户端ID（通常是用户ID）
 * @param {object} options - 配置选项
 * @returns {WebSocketClient}
 */
export function createWebSocketClient(clientId, options = {}) {
  // 根据环境确定 WebSocket URL
  // 开发环境：直接连接到后端
  // 生产环境：使用与 HTTP 相同的域名
  let wsHost, wsProtocol
  
  if (import.meta.env.DEV) {
    // 开发环境：直接连接后端 WebSocket
    wsProtocol = 'ws:'
    wsHost = '127.0.0.1:8000'
  } else {
    // 生产环境：使用与页面相同的协议
    wsProtocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'
    wsHost = window.location.host // 使用当前域名
  }
  
  const url = `${wsProtocol}//${wsHost}/ws/${clientId}`
  console.log(`[WebSocket] 连接 URL: ${url}`)
  
  return new WebSocketClient(url, options)
}

/**
 * 获取默认的 WebSocket 客户端（使用当前用户ID）
 */
export function getDefaultWebSocketClient() {
  const userId = localStorage.getItem('user_id') || 'guest'
  return createWebSocketClient(userId)
}

export default WebSocketClient

